異なるAWSアカウントのDynamoDBにLambdaでアクセスする (CloudFormationで作成)
異なるAWSアカウントにあるDynamoDBテーブルに対して、Lambdaでアクセスする方法を試してみました。
おすすめの方
- 異なるAWSアカウントのリソースにLambdaでアクセスしたい
- CloudFormation、AWS SAMで上記の仕組みを構築したい
アクセスする側のデプロイ(LambdaとIAMロール)
まずは、DynamoDBテーブルにアクセスする側のLambdaとIAMロールをデプロイします。
SAM Init
sam init \ --runtime python3.8 \ --name Cross-Access-Lambda-Sample \ --app-template hello-world \ --package-type Zip
SAMテンプレート
Lambda用のIAMロールを作成しています。この時点では、アクセスされる側のIAMロールは未作成ですが、名前は事前に決めておきます。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Parameters: DstAwsAccountId: Type: String DstRoleName: Type: String TableName: Type: String Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.8 Timeout: 5 Environment: Variables: DST_AWS_ACCOUNT_ID: !Ref DstAwsAccountId DST_ROLE_NAME: !Ref DstRoleName TABLE_NAME: !Ref TableName Role: !GetAtt HelloWorldFunctionRole.Arn Events: HelloWorld: Type: Api Properties: Path: /hello Method: get HelloWorldFunctionLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${HelloWorldFunction} HelloWorldFunctionRole: Type: AWS::IAM::Role Properties: RoleName: other-account-dynamodb-access-lambda-role AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: sts:AssumeRole Principal: Service: lambda.amazonaws.com Policies: - PolicyName: other-account-dynamodb-access-lambda-policy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: "*" - Effect: Allow Action: - sts:AssumeRole Resource: !Sub arn:aws:iam::${DstAwsAccountId}:role/${DstRoleName} Outputs: HelloWorldApi: Value: !Sub https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/
Lambdaコード
アクセスされる側のIAMロールを指定し、AssumeRoleを行います。
boto3で「AssumeRoleの結果として得たAWSアクセスキー」を使って、DynamoDBテーブルにアクセスし、scan()
を実行しています。
import os import json import boto3 DST_AWS_ACCOUNT_ID = os.environ['DST_AWS_ACCOUNT_ID'] DST_ROLE_NAME = os.environ['DST_ROLE_NAME'] TABLE_NAME = os.environ['TABLE_NAME'] def lambda_handler(event, context): sts = boto3.client('sts') access_info = sts.assume_role( RoleArn=f'arn:aws:iam::{DST_AWS_ACCOUNT_ID}:role/{DST_ROLE_NAME}', RoleSessionName='cross_acct_lambda' ) dynamodb = boto3.resource( 'dynamodb', aws_access_key_id=access_info['Credentials']['AccessKeyId'], aws_secret_access_key=access_info['Credentials']['SecretAccessKey'], aws_session_token=access_info['Credentials']['SessionToken'], ) table = dynamodb.Table(TABLE_NAME) res = table.scan() return { 'statusCode': 200, 'body': json.dumps( res.get('Items', []) ), }
デプロイ
デプロイします。アクセスされる側のAWSアカウントIDとIAMロール名もパラメータで付与しています。
sam build sam package \ --output-template-file packaged.yaml \ --s3-bucket cm-fujii.genki-deploy sam deploy \ --template-file packaged.yaml \ --stack-name Cross-Access-Lambda-Sample-Stack \ --s3-bucket cm-fujii.genki-deploy \ --parameter-overrides \ DstAwsAccountId=222222222222 \ DstRoleName=dynamodb-access-role \ TableName=access-sample-table \ --capabilities CAPABILITY_NAMED_IAM \ --no-fail-on-empty-changeset
動作確認用にAPIエンドポイントを取得
aws cloudformation describe-stacks \ --stack-name Cross-Access-Lambda-Sample-Stack \ --query 'Stacks[].Outputs'
APIにアクセスすると、失敗する
$ curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/ {"message": "Internal server error"}
CloudWatchのログを見ると、アクセスされる側のIAMロールに対して、AssumeRoleが失敗しています。 この時点では、まだ存在していないからです。
[ERROR] ClientError: An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:sts::111111111111:assumed-role/other-account-dynamodb-access-lambda-role/Cross-Access-Lambda-Sample-Stac-HelloWorldFunction-MoDfF3dqCFI3 is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::222222222222:role/dynamodb-access-role
アクセスされる側のデプロイ
DynamoDBテーブルとIAMロールをデプロイします。
CloudFormationテンプレート
AssumeRole元として、アクセスする側のIAMロール(Lambdaが使うIAMロール)を指定します。 これによって、Lambdaが使うIAMロール以外はAssumeRoleができません。
AWSTemplateFormatVersion: 2010-09-09 Description: DynamoDB and IAM Role Parameters: SrcAwsAccountId: Type: String Resources: AccessSampleTable: Type: AWS::DynamoDB::Table Properties: TableName: access-sample-table BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: deviceId AttributeType: S KeySchema: - AttributeName: deviceId KeyType: HASH DynamodbAccessRole: Type: AWS::IAM::Role Properties: RoleName: dynamodb-access-role AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: sts:AssumeRole Principal: AWS: # AWSアカウントIDだけの指定も可能だが、セキュリティ的な意味でに強く非推奨 # - !Sub ${SrcAwsAccountId} - !Sub arn:aws:iam::${SrcAwsAccountId}:role/other-account-dynamodb-access-lambda-role Policies: - PolicyName: dynamodb-access-policy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - dynamodb:Scan Resource: - !GetAtt AccessSampleTable.Arn MaxSessionDuration: 3600 Outputs: DynamodbAccessRoleArn: Value: !GetAtt DynamodbAccessRole.Arn
デプロイ
アクセスする側のAWSアカウントIDをパラメータで付与しています。
aws cloudformation deploy \ --template-file dynamodb_and_iam.yaml \ --stack-name DynamoDB-and-IAM-Stack \ --parameter-overrides \ SrcAwsAccountId=111111111111 \ --capabilities CAPABILITY_NAMED_IAM
DynamoDBテーブルにデータを格納する
適当にデータを格納しました。
あらためてAPIアクセスし、異なるAWSアカウントのDynamoDBからデータを取得する
無事に取得できました!!
$ curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/ [{"name": "Fooooooo", "deviceId": "d0002"}, {"name": "This is a pen.", "deviceId": "d0001"}]
さいごに
異なるアカウントのリソースにアクセスする方法の助けになれば幸いです。